package com.common.util.sftp;

import com.common.util.localdate.DateTimeUtil;
import com.google.common.collect.Lists;
import com.jcraft.jsch.*;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert;

import java.io.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Predicate;

/**
 * 文件描述 SFTP工具类
 *
 * @author
 * @date
 */
@Getter
@Slf4j(topic = "SFTP工具")
public class SftpUtil {

    /**
     * ssh 默认端口
     */
    private static final int DEFAULT_SFTP_PORT = 22;

    /**
     * 通道类型
     */
    private static final String CHANNEL_TYPE = "sftp";

    /**
     * 无文件
     */
    private static final String NO_SUCH_FILE_STR = "no such file";

    /**
     * ssh会话对象
     */
    private Session sshSession;

    /**
     * 通道连接对象
     */
    private ChannelSftp channelSftp;

    /**
     * 用户名
     */
    private String username;

    /**
     * 用户登录密码
     */
    private String password;

    /**
     * 连接服务器主机地址
     */
    private String host;

    /**
     * 连接服务器端口
     */
    private int port;

    /**
     * ssh 会话配置
     */
    private static final Properties SSH_CONFIG = new Properties();

    static {
        // 设置主机公钥确认为no
        SSH_CONFIG.put("StrictHostKeyChecking", "no");
    }

    public SftpUtil(ChannelSftp channelSftp) {
        this.channelSftp = channelSftp;
    }

    public SftpUtil(String username, String password, String host) {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = DEFAULT_SFTP_PORT;
    }

    public SftpUtil(String username, String password, String host, int port) {
        this.username = username;
        this.password = password;
        this.host = host;
        this.port = (0 == port) ? DEFAULT_SFTP_PORT : port;
    }

    public static class Builder {
        private String username;
        private String password;
        private String host;
        private int port;

        public Builder username(String username) {
            this.username = username;
            return this;
        }

        public Builder password(String password) {
            this.password = password;
            return this;
        }

        public Builder host(String host) {
            this.host = host;
            return this;
        }

        public Builder port(int port) {
            this.port = port;
            return this;
        }

        public SftpUtil build() {
            return new SftpUtil(username, password, host, port);
        }
    }

    /**
     * sftp连接
     */
    public void connect() {
        try {
            JSch jSch = new JSch();
            // 1.创建会话
            sshSession = jSch.getSession(username, host, port);
            // 2.设置会话密码
            sshSession.setPassword(password);
            // 3.配置连接会话属性
            sshSession.setConfig(SSH_CONFIG);
            // 4.会话连接
            sshSession.connect();
            // 5.打开会话通道
            channelSftp = (ChannelSftp) sshSession.openChannel(CHANNEL_TYPE);
            // 6.通道连接
            channelSftp.connect();
        } catch (Exception e) {
            log.error("连接sftp出错", e);
            throw new RuntimeException("连接sftp出错");
        }
    }

    /**
     * 关闭sftp连接
     */
    public void disConnect() {
        try {
            // 1.关闭通道
            // log.info("断开连接前通道isConnected={}", null == this.channelSftp ? false : this.channelSftp.isConnected());
            if (null != this.channelSftp && this.channelSftp.isConnected()) {
                this.channelSftp.disconnect();
                // log.info("断开连接后通道isConnected={}", this.channelSftp.isConnected());
            }
            // 2.关闭会话
            // log.info("断开连接前ssh会话isConnected={}", null == this.sshSession ? false : this.sshSession.isConnected());
            if (null != this.sshSession && this.sshSession.isConnected()) {
                this.sshSession.disconnect();
                // log.info("断开连接后ssh会话.isConnected={}", this.sshSession.isConnected());
            }
        } catch (Exception e) {
            log.error("关闭sftp连接出错", e);
            throw new RuntimeException("关闭sftp连接出错");
        }
    }

    /**
     * 文件信息查看
     *
     * @param logFilePathReg
     * @param selector
     */
    public void ls(String logFilePathReg, ChannelSftp.LsEntrySelector selector) {
        String curDateTime = DateTimeUtil.formatDateTime(LocalDateTime.now(), DateTimeUtil.FORMATTER_DATETIME_YMDHMS_YES);
        try {
            log.info("当前{}查看{}的文件路径为 {}", curDateTime, remoteSiteInfo(), "ll " + logFilePathReg);
            // 文件查看
            this.channelSftp.ls(logFilePathReg, selector);
        } catch (Exception e) {
            log.error("{}获取{}请求时间的所有日志文件全路径出错", curDateTime, remoteSiteInfo(), e);
            // throw new RuntimeException("获取请求时间的服务端的所有日志文件全路径出错");
        }
    }

    /**
     * 文件信息查看
     *
     * @param logFilePathReg
     */
    public Vector ls(String logFilePathReg) {
        String curDateTime = DateTimeUtil.formatDateTime(LocalDateTime.now(), DateTimeUtil.FORMATTER_DATETIME_YMDHMS_YES);
        Vector result = new Vector();
        try {
            log.info("当前{}查看{}的文件路径为 {}", curDateTime, remoteSiteInfo(), "ll " + logFilePathReg);
            // 文件查看
            result = this.channelSftp.ls(logFilePathReg);
        } catch (Exception e) {
            log.error("{}获取{}请求时间的所有日志文件全路径出错", curDateTime, remoteSiteInfo(), e);
            // throw new RuntimeException("获取请求时间的服务端的所有日志文件全路径出错");
        }

        return result;
    }

    /**
     * 判断远程文件目录是否存在
     *
     * @param remoteDir 远程文件目录
     * @return
     */
    public boolean isDirExist(String remoteDir) {
        boolean isDirExistFlag = false;
        try {
            SftpATTRS sftpATTRS = this.channelSftp.lstat(remoteDir);
            isDirExistFlag = sftpATTRS.isDir();
        } catch (SftpException e) {
            log.error("查看{}给定文件目录{}是否存在出错", remoteSiteInfo(), remoteDir, e);
            if (StringUtils.equals(e.getMessage().toLowerCase(), NO_SUCH_FILE_STR)) {
                isDirExistFlag = false;
            }
        }

        return isDirExistFlag;
    }

    /**
     * 判断远程服务器上的指定文件是否存在
     *
     * @param remoteFileFullPath 远程文件全路径
     * @return boolean
     */
    public boolean isFileExist(String remoteFileFullPath) {
        boolean isFileExistFlag = false;
        try {
            SftpATTRS sftpATTRS = this.channelSftp.lstat(remoteFileFullPath);
            if (sftpATTRS.isDir()) {
                log.warn("{}的日志文件{}不存在，该路径为目录", remoteSiteInfo(), remoteFileFullPath);
                isFileExistFlag = false;
            } else {
                isFileExistFlag = true;
            }
        } catch (Exception e) {
            log.error("查看{}给定文件{}是否存在出错", remoteSiteInfo(), remoteFileFullPath, e);
            if (StringUtils.equals(e.getMessage().toLowerCase(), NO_SUCH_FILE_STR)) {
                isFileExistFlag = false;
            }
        }

        return isFileExistFlag;
    }

    /**
     * 将远程文件复制到当前服务所在的目标文件中
     *
     * @param remoteSrcFileFullPath 远程文件全路径
     * @param localDstFileFullPath  本地目标文件全路径
     * @return
     */
    public boolean copyFile(String remoteSrcFileFullPath, String localDstFileFullPath) {
        log.info("复制{}文件远程路径{}到本地目标路径{}", remoteSiteInfo(), remoteSrcFileFullPath, localDstFileFullPath);
        try {
            boolean remoteFileExist = this.isFileExist(remoteSrcFileFullPath);
            String dstFileDirPath = localDstFileFullPath.substring(0, localDstFileFullPath.lastIndexOf(File.separator));
            File dstFileDir = new File(dstFileDirPath);
            // 本地路径存在
            if (!dstFileDir.exists() || !dstFileDir.isDirectory()) {
                log.warn("服务本地存放目录{}不存在", dstFileDir);
                return false;
            }
            if (remoteFileExist) {
                // 将文件复制到本地
                this.channelSftp.get(remoteSrcFileFullPath, localDstFileFullPath);
                return true;
            }
        } catch (Exception e) {
            log.error("复制{}文件{}到服务本地文件{}出错", remoteSiteInfo(), remoteSrcFileFullPath, localDstFileFullPath, e);
        }

        return false;
    }

    /**
     * 获取远程文件的输入流
     *
     * @param remoteSrcFileFullPath 远程文件全路径
     * @return InputStream
     */
    public InputStream getInputStream(String remoteSrcFileFullPath) {
        InputStream inputStream = null;
        try {
            boolean remoteFileExist = this.isFileExist(remoteSrcFileFullPath);
            if (remoteFileExist) {
                inputStream = this.channelSftp.get(remoteSrcFileFullPath);
            }
        } catch (Exception e) {
            log.error("获取{}文件{}输入流出错", remoteSiteInfo(), remoteSrcFileFullPath, e);
        }

        return inputStream;
    }

    /**
     * 服务器信息
     *
     * @return String
     */
    public String remoteSiteInfo() {
        return String.format("服务器%s-服务节点%s", this.host, this.username);
    }

    /**
     * 批量下载文件
     *
     * @param remotePath：远程下载目录(以路径符号结束,可以为相对路径)
     * @param localPath：本地保存目录(以路径符号结束)
     * @param fileFormat：下载文件格式(以特定字符开头,为空不做检验)
     * @param fileEndFormat：下载文件格式(文件格式)
     * @param del：下载后是否删除sftp文件
     * @param intervalMinutes                    间隔时间（分钟）
     * @param checkSuccessData                   检查成功数据
     * @return List<String>
     */
    public List<String> batchDownLoadFile(String remotePath, String localPath, String fileFormat, String fileEndFormat,
                                          boolean del, String intervalMinutes, String checkSuccessData) {
        List<String> fileNames = Lists.newArrayList();
        if (StringUtils.isEmpty(remotePath) || StringUtils.isEmpty(localPath)) {
            log.error("source  file path is not exists or target file path is not exists");
            return fileNames;
        }

        try {
            int i1 = Integer.parseInt(intervalMinutes);
            for (int i = i1; i >= 1; i--) {
                Date date = new Date();
                Long timestamp = date.getTime() - (i * 60 * 1000);
                LocalDateTime now = DateTimeUtil.timestamp2Date(timestamp);
                // 生成时间并且校验成功并且保存数据地址
                String path = getLocalPathFilePath(checkSuccessData, now);
                path = path.replace("\\", "/");
                log.info("本地地址{}", path);
                // 获取本地文件
                List<String> localFileList = getAllFile(path);
                if (localFileList.size() > 0 && localFileList.size() == 60) {
                    log.info("数据正常");
                    fileNames = new ArrayList<>();
                } else {
                    fileNames = getSourceFiles(fileNames, remotePath, now, path, fileFormat, fileEndFormat,
                            del, localFileList, intervalMinutes, localPath);
                }
            }
        } catch (SftpException e) {
            log.error("batchDownLoadFile出错", e);
        } finally {
            // this.disconnect();
        }

        return fileNames;
    }

    /**
     * 获取源文件
     */
    private List<String> getSourceFiles(List<String> filenames, String remotePath, LocalDateTime now, String path, String fileFormat,
                                        String fileEndFormat, boolean del, List<String> localFileList, String intervalMinutes, String localPath) throws SftpException {
        String paths = getLocalPathFilePath(remotePath, now);
        paths = paths.replace("\\", "/");
        Vector v = listFiles(paths);
        if (v.size() > 0) {
            log.info("本次处理文件个数不为零,开始下载...fileSize=" + v.size());
            Iterator it = v.iterator();
            while (it.hasNext()) {
                ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) it.next();
                String filename = entry.getFilename();
                SftpATTRS attrs = entry.getAttrs();

                if (localFileList.size() > 0) {
                    for (String fileUrl : localFileList) {
                        if (fileUrl.contains(filename)) {
                            log.warn("文件已经采集过，fileName = {}", filename);
                            continue;
                        }
                    }
                }

                if (isDirExist(path + filename)) {
                    log.warn("文件已经采集过，fileName = {}", filename);
                    continue;
                }

                if (!attrs.isDir()) {
                    boolean flag = false;
                    String localFileName = paths + filename;
                    fileFormat = fileFormat == null ? "" : fileFormat
                            .trim();
                    fileEndFormat = fileEndFormat == null ? ""
                            : fileEndFormat.trim();
                    // 三种情况
                    if (fileFormat.length() > 0 && fileEndFormat.length() > 0) {
                        if (filename.startsWith(fileFormat) && filename.endsWith(fileEndFormat)) {
                            flag = downloadFile(paths, filename, path, filename, localPath);
                            if (flag) {
                                filenames.add(localFileName);
                                if (flag && del) {
                                    // todo 后期数据是否要删除
                                    // deleteSFTP(remotePath, filename);
                                }
                            }
                        }
                    } else if (fileFormat.length() > 0 && "".equals(fileEndFormat)) {
                        if (filename.startsWith(fileFormat)) {
                            flag = downloadFile(paths, filename, path, filename, localPath);
                            if (flag) {
                                filenames.add(localFileName);
                                if (flag && del) {
                                    // todo 后期数据是否要删除
                                    // deleteSFTP(remotePath, filename);
                                }
                            }
                        }
                    } else if (fileEndFormat.length() > 0 && "".equals(fileFormat)) {
                        if (filename.endsWith(fileEndFormat)) {
                            flag = downloadFile(paths, filename, path, filename, localPath);
                            if (flag) {
                                filenames.add(localFileName);
                                if (flag && del) {
                                    // todo 后期数据是否要删除
                                    // deleteSFTP(remotePath, filename);
                                }
                            }
                        }
                    } else {
                        flag = downloadFile(paths, filename, path, filename, localPath);
                        if (flag) {
                            filenames.add(localFileName);
                            if (flag && del) {
                                // todo 后期数据是否要删除
                                // deleteSFTP(remotePath, filename);
                            }
                        }
                    }
                }
            }

        }
        if (log.isInfoEnabled()) {
            log.info("download file is success:remotePath=" + remotePath
                    + "and localPath=" + paths + ",file size is"
                    + v.size());
        }
        return filenames;
    }

    /**
     * 当前时间拼接路径
     */
    private String getLocalPathFilePath(String localPath, LocalDateTime curDateTime) {

        int year = curDateTime.getYear();
        int month = curDateTime.getMonthValue();
        int day = curDateTime.getDayOfMonth();
        int hh = curDateTime.getHour();
        int mm = curDateTime.getMinute();
        String filePath = localPath +
                File.separator + year +
                File.separator + month +
                File.separator + day +
                File.separator + hh +
                File.separator + mm +
                File.separator;
        return filePath;
    }


    /**
     * 下载单个文件
     *
     * @param remotePath：远程下载目录(以路径符号结束)
     * @param remoteFileName：下载文件名
     * @param localPath：本地保存目录(以路径符号结束)
     * @param localFileName：保存文件名
     * @return
     */
    public boolean downloadFile(String remotePath, String remoteFileName, String path, String localFileName, String localPath) {
        FileOutputStream fieloutput = null;
        try {
            // 进入远程目录
            // sftp.cd(remotePath);
            // 下载到可以访问到文件的目录
            File file = new File(localPath + localFileName);
            // 文件如果已经存在 是否覆盖
            if (file.exists()) {
                log.info("File is exists : " + localFileName);
                return false;
            }
            // 创建本地文件夹
            mkdirs(localPath + localFileName);
            fieloutput = new FileOutputStream(file);
            // 把线上服务的文件内容写到 本地数如流里面
            this.channelSftp.get(remotePath + remoteFileName, fieloutput);

            if (log.isInfoEnabled()) {
                log.info("downloadFile {} success from sftp.", remoteFileName);
            }
            return true;
        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException", e);
        } catch (SftpException e) {
            log.error("SftpException", e);
        } finally {
            if (null != fieloutput) {
                try {
                    fieloutput.close();
                } catch (IOException e) {
                    log.error("IOException", e);
                }
            }
        }

        return false;
    }

    /**
     * 上传单个文件
     *
     * @param remotePath：远程保存目录
     * @param remoteFileName：保存文件名
     * @param localPath：本地上传目录(以路径符号结束)
     * @param localFileName：上传的文件名
     * @return
     */
    public boolean uploadFile(String remotePath, String remoteFileName, String localPath, String localFileName) {
        FileInputStream in = null;
        try {
            createDir(remotePath);
            File file = new File(localPath + localFileName);
            in = new FileInputStream(file);
            this.channelSftp.put(in, remoteFileName);
            return true;
        } catch (FileNotFoundException e) {
            log.error("FileNotFoundException", e);
        } catch (SftpException e) {
            log.error("SftpException", e);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error("IOException", e);
                }
            }
        }
        return false;
    }

    /**
     * 批量上传文件
     *
     * @param remotePath：远程保存目录
     * @param localPath：本地上传目录(以路径符号结束)
     * @param fileFormat：下载文件格式(以特定字符开头,为空不做检验)
     * @param fileEndFormat：下载文件格式(文件格式)
     * @param del：上传后是否删除本地文件
     * @return
     */
    public boolean batchUploadFile(String remotePath, String localPath, String fileFormat, String fileEndFormat,
                                   boolean del, String backupPath) {
        try {
            connect();
            File file = new File(localPath);
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (!files[i].isFile()) {
                    continue;
                }
                if (StringUtils.isNotEmpty(fileFormat) && !files[i].getName().startsWith(fileFormat)) {
                    continue;
                }
                if (StringUtils.isNotEmpty(fileEndFormat) && !files[i].getName().startsWith(fileEndFormat)) {
                    continue;
                }

                if (this.uploadFile(remotePath, files[i].getName(),
                        localPath, files[i].getName())
                        && del) {
                    deleteFile(localPath + files[i].getName());
                }
            }
            if (log.isInfoEnabled()) {
                log.info("upload file is success:remotePath=" + remotePath
                        + "and localPath=" + localPath + ",file size is "
                        + files.length);
            }
            return true;
        } catch (Exception e) {
            log.error("Exception", e);
        } finally {
            this.disConnect();
        }
        return false;

    }

    /**
     * 删除本地文件目录
     *
     * @param filePath
     * @return
     */
    public boolean deleteFile(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            return false;
        }

        if (!file.isFile()) {
            return false;
        }
        boolean rs = file.delete();
        if (rs && log.isInfoEnabled()) {
            log.info("delete file success from local.");
        }
        return rs;
    }

    /**
     * 创建远程目录
     *
     * @param createPath
     * @return
     */
    public boolean createDir(String createPath) {
        try {
            if (isDirExist(createPath)) {
                this.channelSftp.cd(createPath);
                return true;
            }
            String[] pathArr = createPath.split("/");
            StringBuffer filePath = new StringBuffer("/");
            for (String path : pathArr) {
                if (path.equals("")) {
                    continue;
                }
                filePath.append(path + "/");
                if (isDirExist(filePath.toString())) {
                    this.channelSftp.cd(filePath.toString());
                } else {
                    // 建立目录
                    this.channelSftp.mkdir(filePath.toString());
                    // 进入并设置为当前目录
                    this.channelSftp.cd(filePath.toString());
                }

            }
            // 进入创建好的目录
            this.channelSftp.cd(createPath);
            return true;
        } catch (SftpException e) {
            log.error("SftpException", e);
        }
        return false;
    }

    /**
     * 删除stfp文件
     *
     * @param directory：要删除文件所在目录
     * @param deleteFile：要删除的文件
     */
    public void deleteSFTP(String directory, String deleteFile) {
        try {
            this.channelSftp.rm(directory + deleteFile);
            if (log.isInfoEnabled()) {
                log.info("delete file success from sftp.");
            }
        } catch (Exception e) {
            log.error("Exception", e);
        }
    }

    /**
     * 如果目录不存在就创建目录
     *
     * @param path
     */
    public void mkdirs(String path) {
        File f = new File(path);
        String fs = f.getParent();
        f = new File(fs);
        if (!f.exists()) {
            f.mkdirs();
        }
    }

    /**
     * 文件重命名
     *
     * @param oldPath
     * @param newPath
     */
    public void rename(String oldPath, String newPath) {
        try {
            this.channelSftp.rename(oldPath, newPath);
        } catch (SftpException e) {
            log.error("File rename failed {}->{}出错", oldPath, newPath, e);
        }
    }

    /**
     * 列出目录下的文件
     *
     * @param directory：要列出的目录
     * @return
     * @throws SftpException
     */
    public Vector listFiles(String directory) throws SftpException {
        Vector vector = new Vector();
        /**
         * 过滤linux目录下 '.' 和 '..' 文件
         * */
        boolean isExist = isExistDir(directory);
        if (!isExist) {
            log.info("服务器上不存在文件:{}", directory);
        } else {
            vector = this.channelSftp.ls(directory);
            vector.removeIf(new Predicate<ChannelSftp.LsEntry>() {
                @Override
                public boolean test(ChannelSftp.LsEntry lsEntry) {
                    return "..".equals(lsEntry.getFilename()) || ".".equals(lsEntry.getFilename());
                }
            });
        }

        return vector;
    }

    /**
     * @param path
     * @return
     */
    public boolean isExistDir(String path) {
        boolean isExist = false;
        try {
            SftpATTRS sftpATTRS = this.channelSftp.lstat(path);
            isExist = true;
            return sftpATTRS.isDir();
        } catch (Exception e) {
            if (StringUtils.equals(e.getMessage().toLowerCase(), "no such file")) {
                isExist = false;
            }
        }
        return isExist;

    }

    /**
     * 获取本地文件
     */
    public static List<String> getAllFile(String directoryPath) {
        List<String> list = new ArrayList<String>();
        File baseFile = new File(directoryPath);
        if (baseFile.isFile() || !baseFile.exists()) {
            return list;
        }
        File[] files = baseFile.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                list.add(file.toString());
            } else {
                list.add(file.toString());
            }
        }

        return list;
    }

    /**
     * 创建文件目录
     *
     * @param dir
     */
    public File mkdirs2(String dir) {
        // log.info("待创建本地目录为{}", dir);
        File file = null;
        try {
            file = new File(dir);
            if (!file.exists()) {
                file.mkdirs();
                log.info("创建本地目录{}成功", dir);
            }
        } catch (Exception e) {
            log.error("创建本地目录出错", e);
            throw new RuntimeException("创建本地目录出错");
        }

        return file;
    }

    /**
     * 删除文件目录
     *
     * @param rootFile
     */
    public void delDir(File rootFile) {
        Assert.notNull(rootFile, "待删除文件不能为空");
        // log.info("待删除本地目录为{}", rootFile);
        try {
            if (!rootFile.exists()) {
                log.warn("待删除本地目录{}不存在", rootFile);
                return;
            }

            if (rootFile.isFile()) {
                // 是文件就直接删除
                rootFile.delete();
            } else {
                // 删除目录下的涉及文件
                File[] files = rootFile.listFiles();
                for (File curFile : files) {
                    // 递归删除
                    delDir(curFile);
                }
                // 删除根目录
                rootFile.delete();
            }
        } catch (Exception e) {
            log.error("删除本地目录{}出错", rootFile.getPath(), e);
            // throw new BaseException("删除目录出错");
        }

        log.info("删除本地目录{}成功", rootFile);
    }

    /**
     * 判断文件或目录是否存在
     *
     * @param path 文件或目录路径
     * @return {@code true} 存在 {@code false} 不存在
     */
    public boolean isExist(String path) {
        try {
            channelSftp.lstat(path);
            return true;
        } catch (SftpException e) {
            return false;
        }
    }

    /**
     * 获取远程文件的输入流
     *
     * @param dir  文件目录
     * @param name 文件名
     * @return 远程文件流
     */
    public InputStream getInputStream(String dir, String name) {
        if (!isExist(dir)) {
            throw new RuntimeException(String.format("目录(%s)不存在！", dir));
        }
        String absoluteFilePath = dir + "/" + name;
        if (!isExist(absoluteFilePath)) {
            throw new RuntimeException(String.format("文件(%s)不存在！", absoluteFilePath));
        }
        try {
            channelSftp.cd(dir);
            return channelSftp.get(name);
        } catch (SftpException e) {
            throw new RuntimeException("sftp获取远程文件输入流错误", e);
        }
    }

    /**
     * 下载远程文件
     *
     * @param dir  文件目录
     * @param name 文件名
     * @return 文件字节数组
     */
    public byte[] download(String dir, String name) {
        InputStream in = getInputStream(dir, name);
        return this.inputStreamToByteArray(in);
    }

    /**
     * 输入流转字节数组
     *
     * @param in 输入流
     * @return 字节数组
     */
    public static byte[] inputStreamToByteArray(InputStream in) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024 * 4];
            int n;
            while ((n = in.read(buffer)) > 0) {
                out.write(buffer, 0, n);
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException("输入流转字节数组出错", e);
        }
    }

    /**
     * 上传文件（文件输入流）
     *
     * @param dir  远程目录
     * @param name 远程文件名
     * @param in   输入流
     */
    public void upload(String dir, String name, InputStream in) {
        try {
            mkdirs(dir);
            channelSftp.cd(dir);
            channelSftp.put(in, name);
        } catch (SftpException e) {
            throw new RuntimeException("sftp上传文件出错", e);
        }
    }

    /**
     * 上传文件（文件路径）
     *
     * @param dir  远程目录
     * @param name 远程文件名
     * @param src  本地文件路径
     */
    public void upload(String dir, String name, String src) {
        try {
            mkdirs(dir);
            channelSftp.cd(dir);
            channelSftp.put(src, name);
        } catch (SftpException e) {
            throw new RuntimeException("sftp上传文件出错", e);
        }
    }

    /**
     * 删除目录
     *
     * @param dir 远程目录
     */
    public void delDir(String dir) {
        if (!isDirExist(dir)) {
            return;
        }
        try {
            channelSftp.rmdir(dir);
        } catch (SftpException e) {
            throw new RuntimeException("sftp删除目录出错", e);
        }
    }

    /**
     * 删除文件
     *
     * @param dir  远程目录
     * @param name 远程文件名
     */
    public void delFile(String dir, String name) {
        if (!isDirExist(dir)) {
            return;
        }
        String absoluteFilePath = dir + "/" + name;
        if (!isFileExist(absoluteFilePath)) {
            return;
        }
        try {
            channelSftp.cd(dir);
            channelSftp.rm(name);
        } catch (SftpException e) {
            throw new RuntimeException("sftp删除文件出错", e);
        }
    }

    /**
     * 递归创建目录
     *
     * @param dir 目录绝对路径
     */
    public void mkRemoteDirs(String dir) {
        String[] folders = dir.split("/");
        try {
            channelSftp.cd("/");
            for (String folder : folders) {
                if (folder.length() > 0) {
                    try {
                        channelSftp.cd(folder);
                    } catch (Exception e) {
                        channelSftp.mkdir(folder);
                        channelSftp.cd(folder);
                    }
                }
            }
        } catch (SftpException e) {
            throw new RuntimeException("sftp创建目录出错", e);
        }
    }

    /**
     * 查看远程目录下的文件和目录
     *
     * @param path 远程目录路径
     * @return 目录下的文件和目录名称集合
     */
    public List<String> list(String path) {
        List<String> result = new ArrayList<>();
        ChannelSftp.LsEntrySelector selector = lsEntry -> {
            String filename = lsEntry.getFilename();
            if (!".".equals(filename) && !"..".equals(filename)) {
                result.add(filename);
            }
            return ChannelSftp.LsEntrySelector.CONTINUE;
        };
        try {
            channelSftp.ls(path, selector);
        } catch (SftpException e) {
            throw new RuntimeException("sftp查看目录出错", e);
        }
        return result;
    }

    /**
     * 移动或重命名文件
     *
     * @param src    源文件
     * @param target 目标文件
     */
    public void move(String src, String target) {
        try {
            channelSftp.rename(src, target);
        } catch (SftpException e) {
            throw new RuntimeException("sftp移动文件出错", e);
        }
    }

    /**
     * 修改权限
     *
     * @param permissions 权限，三位0-7的数字
     * @param path        绝对路径
     */
    public void chmod(String permissions, String path) {
        if (permissions == null) {
            throw new RuntimeException("权限不能为null");
        }
        if (permissions.length() != 3) {
            throw new RuntimeException("权限必须是3位0-7的数字");
        }
        for (char c : permissions.toCharArray()) {
            int i;
            try {
                i = Integer.parseInt(String.valueOf(c));
            } catch (NumberFormatException e) {
                throw new RuntimeException("权限必须是3位0-7的数字");
            }
            if (i > 7 || i < 0) {
                throw new RuntimeException("权限必须是3位0-7的数字");
            }
        }
        Integer p = Integer.valueOf(permissions, 8);
        try {
            channelSftp.chmod(p, path);
        } catch (SftpException e) {
            throw new RuntimeException("sftp修改权限出错", e);
        }
    }
}
